/******************************************
@Description:
@Time : 2021/1/18 15:07
@Author :lixiaojun

*******************************************/
package lib

import (
	"crypto/aes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"git.900sui.cn/kc/base/common/functions"
	"git.900sui.cn/kc/kcgin"
	"git.900sui.cn/kc/kcgin/logs"
	"git.900sui.cn/kc/rpcPay/constkey"
	"git.900sui.cn/kc/rpcinterface/interface/order"
	"git.900sui.cn/kc/rpcinterface/interface/pay"
	"github.com/shopspring/decimal"
	"github.com/tidwall/gjson"
	"github.com/wenzhenxi/gorsa"
	"gopkg.in/resty.v1"
	"math/rand"
	"net/url"
	"sort"
	"strings"
	"time"
)

type icbcpay struct {
	appId           string
	myPrivateKey    string //密钥对认证方式，公钥在API平台登记，此处为私钥
	apigwPublicKey  string //网关公钥
	host            string //地址
	onlineMerId     string //线上商户编号
	offlineMerId    string //线下商户编号,例如：二维码支付等等
	notifyUrl       string //通知地址
	frontUrl        string
	merAcct         string
	merPrtclNo      string
	payTimeOut      string
	wxAppId         string //微信appId
	wxOfficialAppId string //微信公众号appId
	wxAppletAppId   string //微信小程序appId
	signType        string
	encryptType     string
	isNeedEncrypt   bool   //是否需要加密
	encryptKey      string //加密key
}

var (
	Loc             *time.Location
	IcbcPay         *icbcpay
	invalidConfData = []string{"", "0"} //无效的配置数据
)

var (
	icbcSignatureErr       = errors.New("Only support CA/RSA signature!")
	icbcVerifySignatureErr = errors.New("Only support CA or RSA signature verify!")
	systemErr              = errors.New("system error")
	requestTypeErr         = errors.New("Only support GET or POST http method!")
)

func init() {
	IcbcPay = &icbcpay{
		appId:           kcgin.AppConfig.String("icbcpay.appId"),
		myPrivateKey:    functions.GetPemPrivate(kcgin.AppConfig.String("icbcpay.myPrivateKey")),
		apigwPublicKey:  functions.GetPemPublic(kcgin.AppConfig.String("icbcpay.apiGwPublicKey")),
		host:            kcgin.AppConfig.String("icbcpay.host"),
		onlineMerId:     kcgin.AppConfig.String("icbcpay.onlineMerId"),
		offlineMerId:    kcgin.AppConfig.String("icbcpay.offlineMerId"),
		merAcct:         kcgin.AppConfig.String("icbcpay.merAcct"),
		merPrtclNo:      kcgin.AppConfig.String("icbcpay.merPrtclNo"),
		payTimeOut:      kcgin.AppConfig.DefaultString("pay.timeout", "1800"),
		wxAppId:         kcgin.AppConfig.String("icbcpay.wxAppId"),
		wxOfficialAppId: kcgin.AppConfig.String("icbcpay.wxOfficialAppId"),
		wxAppletAppId:   kcgin.AppConfig.String("icbcpay.wxAppletAppId"),
		notifyUrl:       kcgin.AppConfig.String("icbcpay.notifyUrl"),
		frontUrl:        kcgin.AppConfig.String("icbcpay.frontUrl") + "/#/serve/consumeResult",
		signType:        constkey.SIGN_TYPE_RSA2,
	}
	Loc, _ = time.LoadLocation("Local")

}

// 二维码支付
// @orderSn 订单编号
// @amount 金额
// @title 支付标题
// @body 支付内容
// @formUrl 前端跳转连接
// @createIP 商户订单生成的机器IP
// @return string 二维码内容
func (i *icbcpay) QrCode(payInfo *pay.PayInfo) (string, error) {
	amount, _ := decimal.NewFromString(payInfo.RealAmount)
	title, body := "商品标题信息", "支付二维码生成"
	now := time.Now()
	commonData := map[string]string{
		constkey.APP_ID:    i.appId,
		constkey.MSG_ID:    GetMsgId(),
		constkey.FORMAT:    constkey.FORMAT_JSON,
		constkey.CHARSET:   constkey.CHARSET_UTF8,
		constkey.SIGN_TYPE: constkey.SIGN_TYPE_RSA2,
		constkey.TIMESTAMP: now.Format(constkey.Date_Time_Format),
	}
	i.signType = commonData[constkey.SIGN_TYPE]
	bizContentData := map[string]string{
		"mer_id":            i.offlineMerId,                               //商户线下档案编号,特约商户12位，特约部门15位
		"out_trade_no":      payInfo.OrderSn,                              //商户系统订单号
		"order_amt":         amount.Mul(decimal.NewFromInt(100)).String(), //订单总金额，单位分
		"trade_date":        now.Format(constkey.Date_Format),             //商户订单生成日期，格式：yyyyMMdd
		"trade_time":        now.Format(constkey.Time_Format),             //商户订单生成时间，格式：HHmmss
		"attach":            fmt.Sprintf("%s-%s", title, body),            //商户附加数据，最多21个汉字字符
		"pay_expire":        i.payTimeOut,                                 //二维码有效期，单位：秒，必须小于24小时1200
		"notify_url":        i.notifyUrl,                                  //商户接收支付成功通知消息URL，当notify_flag为1时必输
		"tporder_create_ip": payInfo.CreateIP,                             //商户订单生成的机器IP
		"sp_flag":           "0",                                          //扫码后是否需要跳转分行，0：否，1：是，默认值0
		"notify_flag":       "1",                                          //商户是否开启通知接口，0-否；1-是，默认值0
	}

	res, err := i.execute(commonData, bizContentData, constkey.UrlCreateQrcode)
	if err != nil {
		return "", err
	}
	qrcode := res.Get(constkey.RESPONSE_BIZ_CONTENT).Get("qrcode")
	if qrcode.Exists() {
		return qrcode.String(), nil
	}
	return "", systemErr
}

//微信小程序支付
func (i *icbcpay) WxappPay(payInfo *pay.PayInfo) (*gjson.Result, error) {
	return i.wxOfficialOrAppletPay(payInfo, i.wxAppletAppId, constkey.ACCESS_TYPE_WXAPPLET)
}

// 微信公众号支付
func (i *icbcpay) PayWxOfficial(payInfo *pay.PayInfo) (*gjson.Result, error) {
	return i.wxOfficialOrAppletPay(payInfo, i.wxOfficialAppId, constkey.ACCESS_TYPE_WXOFFICIAL)
}

// H5支付宝支付
func (i *icbcpay) H5AliPay(payInfo *pay.PayInfo) (string, error) {
	s, err := i.URLValues(payInfo)
	strUrl := s.String()
	return strUrl, err
}

func (i *icbcpay) URLValues(payInfo *pay.PayInfo) (result *url.URL, err error) {
	realAmount, _ := decimal.NewFromString(payInfo.RealAmount)
	amount := fmt.Sprintf("%012v", realAmount.Mul(decimal.NewFromInt(100)))
	title, body := "商品标题信息", "H5支付宝消费下单"
	var p = url.Values{}
	// 公共参数
	p.Add(constkey.APP_ID, i.appId)                                         // 必须 APP的编号
	p.Add(constkey.MSG_ID, GetMsgId())                                      // 必须 消息通讯唯一编号
	p.Add(constkey.FORMAT, constkey.FORMAT_JSON)                            // 可选 请求参数格式，仅支持json
	p.Add(constkey.CHARSET, constkey.CHARSET_UTF8)                          // 可选 字符集，缺省为UTF-8
	p.Add(constkey.SIGN_TYPE, constkey.SIGN_TYPE_RSA2)                      // 可选 加密方式
	p.Add(constkey.TIMESTAMP, time.Now().Format(constkey.Date_Time_Format)) // 必须 交易发生时间戳

	// biz_content
	bizContentMap := map[string]string{
		"mer_id":          i.onlineMerId,     // 必须 商户编号
		"mer_prtcl_no":    i.merPrtclNo,      // 必须 收单产品协议编号
		"order_id":        payInfo.OrderSn,   // 必须 商户订单号，只能是数字、大小写字母，且在同一个商户号下唯一
		"order_date_time": getOrigDateTime(), // 必须 交易日期时间，格式为yyyy-MM-dd'T'HH:mm:ss
		"amount":          amount,
		"cur_type":        constkey.FEE_TYPE_CNY,   // 必须 交易币种，目前工行只支持使用人民币（001）支付
		"body":            title + "-" + body,      // 必须 商品描述
		"notify_url":      i.notifyUrl,             // 必须 异步通知商户URL，端口必须为443或80
		"icbc_appid":      i.appId,                 // 必须 工行API平台的APPID
		"notify_type":     constkey.NOTIFY_TYPE_HS, // 必须 通知类型，表示在交易处理完成后把交易结果通知商户的处理模式。 取值“HS”：在交易完成后将通知信息，主动发送给商户，发送地址为mer_url指定地址； 取值“AG”：在交易完成后不通知商户

		"expireTime":    i.payTimeOut,           // 可选 订单失效时间，单位为秒，建议大于60秒//异步通知商户URL，端口必须为443或80
		"result_type":   constkey.RESULT_TYPE_0, // 可选 结果发送类型
		"pay_limit":     "",                     // 可选 支付方式限定
		"return_url":    "",                     // 可选 支付成功回显页面
		"quit_url":      "",                     // 可选 用户付款中途退出返回商户网站的地址（仅对浏览器内支付时有效
		"order_apd_inf": "",                     // 可选 订单附加信息
		"detail":        "",                     // 可选 商品详细描述
		"attach":        "",                     // 可选 附加数据，在查询API和支付通知中原样返回，该字段主要用于商户携带订单的自定义数据
	}

	bytes, err := json.Marshal(bizContentMap)
	if err != nil {
		return nil, err
	}

	p.Add("biz_content", string(bytes))
	var pList = make([]string, 0, 0)
	for key := range p {
		var value = strings.TrimSpace(p.Get(key))
		if len(value) > 0 {
			pList = append(pList, key+"="+value)
		}
	}

	sort.Strings(pList)
	var src = strings.Join(pList, "&")

	src = constkey.UrlZfu + "?" + src
	// Sign 方法中对拼接后的参数进行编码处理
	sign, err := i.Sign(src)
	if err != nil {
		return nil, err
	}
	p.Add("sign", sign)
	result, err = url.Parse(i.host + constkey.UrlZfu + "?" + p.Encode())
	if err != nil {
		return nil, err
	}
	return result, err
}

/**
* accessType:收单接入方式：7-微信公众号，9-微信小程序
* shopAppId:微信小程序或者微信公众号appId
 */
// 微信公众号和微信小程序支付
func (i *icbcpay) wxOfficialOrAppletPay(payInfo *pay.PayInfo, shopAppId, accessType string) (*gjson.Result, error) {
	amount, _ := decimal.NewFromString(payInfo.RealAmount)
	title, body := "商品标题信息", ""
	if accessType == constkey.ACCESS_TYPE_WXOFFICIAL { //微信公众号
		body = "微信公众号支付"
	} else { //微信小程序
		body = "微信小程序支付"
	}
	now := time.Now()
	commonData := map[string]string{
		constkey.APP_ID:    i.appId,
		constkey.MSG_ID:    GetMsgId(),
		constkey.FORMAT:    constkey.FORMAT_JSON,
		constkey.CHARSET:   constkey.CHARSET_UTF8,
		constkey.SIGN_TYPE: constkey.SIGN_TYPE_RSA2,
		constkey.TIMESTAMP: now.Format(constkey.Date_Time_Format),
		//constkey.ENCRYPT_TYPE: constkey.ENCRYPT_TYPE_AES,
	}
	i.signType = commonData[constkey.SIGN_TYPE]
	attach := title + "-" + body
	bizContentData := map[string]string{
		"mer_id":           i.onlineMerId,                                //商户编号
		"out_trade_no":     payInfo.OrderSn,                              //商户订单号，只能是数字、大小写字母，且在同一个商户号下唯一
		"pay_mode":         constkey.PAY_MODE_WX,                         //支付方式，9-微信；10-支付宝；13-云闪付
		"access_type":      accessType,                                   //收单接入方式，5-APP，7-微信公众号，8-支付宝生活号，9-微信小程序;
		"mer_prtcl_no":     i.merPrtclNo,                                 //收单产品协议编号
		"orig_date_time":   getOrigDateTime(),                            //交易日期时间，格式为yyyy-MM-dd'T'HH:mm:ss
		"decive_info":      i.aesKey(16),                                 //设备号？
		"body":             fmt.Sprintf("%s-%s", title, body),            //商品描述，商品描述交易字段格式根据不同的应用场景按照以下格式：1）PC网站：传入浏览器打开的网站主页title名-实际商品名称 ；2）公众号：传入公众号名称-实际商品名称；3）H5：传入浏览器打开的移动网页的主页title名-实际商品名称；4）线下门店：门店品牌名-城市分店名-实际商品名称；5）APP：传入应用市场上的APP名字-实际商品名称
		"fee_type":         constkey.FEE_TYPE_CNY,                        //交易币种，目前工行只支持使用人民币（001）支付
		"spbill_create_ip": payInfo.CreateIP,                             //用户端IP
		"total_fee":        amount.Mul(decimal.NewFromInt(100)).String(), //订单金额，单位为分
		"mer_url":          i.notifyUrl,                                  //异步通知商户URL，端口必须为443或80
		"shop_appid":       shopAppId,                                    //商户在微信开放平台注册的APPID，支付方式为微信时不能为空
		"icbc_appid":       i.appId,                                      //商户在工行API平台的APPID
		"open_id":          payInfo.OpenId,                               //第三方用户标识，商户在微信公众号内或微信小程序内接入时必送，即access_type为7或9时，上送用户在商户APPID下的唯一标识；商户通过支付宝生活号接入时不送
		"mer_acct":         i.merAcct,                                    //商户账号，商户入账账号，只能交易时指定
		"expire_time":      i.payTimeOut,                                 //订单失效时间，单位为秒，建议大于60秒
		"attach":           attach,                                       //附加数据，在查询API和支付通知中原样返回，该字段主要用于商户携带订单的自定义数据
		"order_apd_inf":    "",                                           //订单附加信息
		"detail":           "",                                           //商品详细描述，对于使用单品优惠的商户，该字段必须按照规范上传。微信与支付宝的规范不同，请根据支付方式对应相应的规范上送，详细内容参考文末说明
		"notify_type":      constkey.NOTIFY_TYPE_HS,                      //通知类型，表示在交易处理完成后把交易结果通知商户的处理模式。 取值“HS”：在交易完成后将通知信息，主动发送给商户，发送地址为mer_url指定地址； 取值“AG”：在交易完成后不通知商户
		"result_type":      constkey.RESULT_TYPE_0,                       //结果发送类型，通知方式为HS时有效。取值“0”：无论支付成功或者失败，银行都向商户发送交易通知信息；取值“1”，银行只向商户发送交易成功的通知信息。默认是"0"
		"pay_limit":        "",                                           //支付方式限定，上送”no_credit“表示不支持信用卡支付；上送“no_balance”表示仅支持银行卡支付；不上送或上送空表示无限制
		"return_url":       "",                                           //支付成功回显页面，支付成功后，跳转至该页面显示。当access_type=5且pay_mode=10才有效
		"quit_url":         "",                                           //用户付款中途退出返回商户网站的地址（仅对浏览器内支付时有效）当access_type=5且pay_mode=10才有效
	}
	res, err := i.execute(commonData, bizContentData, constkey.UrlConsumepurchase)
	if err != nil {
		return nil, err
	}
	responseBizContent := res.Get(constkey.RESPONSE_BIZ_CONTENT)
	var res2 gjson.Result
	if responseBizContent.Get(constkey.RETURN_CODE).Int() == 0 {
		res2 = responseBizContent.Get(constkey.WX_DATA_PACKAGE) //微信支付数据包
	}
	return &res2, nil
}

//微信/支付宝 App支付
func (i *icbcpay) PayApp(payInfo *pay.PayInfo) (*gjson.Result, error) {
	title, body := "商品标题信息", ""
	now := time.Now()
	commonData := map[string]string{
		constkey.APP_ID:    i.appId,
		constkey.MSG_ID:    GetMsgId(),
		constkey.FORMAT:    constkey.FORMAT_JSON,
		constkey.CHARSET:   constkey.CHARSET_UTF8,
		constkey.SIGN_TYPE: constkey.SIGN_TYPE_RSA2,
		constkey.TIMESTAMP: now.Format(constkey.Date_Time_Format),
		//constkey.ENCRYPT_TYPE: constkey.ENCRYPT_TYPE_AES,
	}
	i.signType = commonData[constkey.SIGN_TYPE]
	realAmount, _ := decimal.NewFromString(payInfo.RealAmount)
	payMode := ""
	if payInfo.ChosePayType == order.CHOSE_PAY_TYPE_ICBC_ALI { //工行支付宝
		payMode, body = constkey.PAY_MODE_ZFB, "工行支付宝支付"
	} else if payInfo.ChosePayType == order.CHOSE_PAY_TYPE_ICBC_WX { //工行微信
		payMode, body = constkey.PAY_MODE_WX, "工行微信支付"
	}
	bizContentData := map[string]string{
		"mer_id":           i.onlineMerId,                                    //商户编号
		"out_trade_no":     payInfo.OrderSn,                                  //商户订单号，只能是数字、大小写字母，且在同一个商户号下唯一
		"pay_mode":         payMode,                                          //支付方式，9-微信；10-支付宝；13-云闪付
		"access_type":      constkey.ACCESS_TYPE_APP,                         //收单接入方式，5-APP，7-微信公众号，8-支付宝生活号，9-微信小程序;这里只能是7/9，暂时使用9
		"mer_prtcl_no":     i.merPrtclNo,                                     //收单产品协议编号
		"orig_date_time":   getOrigDateTime(),                                //交易日期时间，格式为yyyy-MM-dd'T'HH:mm:ss
		"decive_info":      i.aesKey(16),                                     //设备号？
		"body":             fmt.Sprintf("%s-%s", title, body),                //商品描述，商品描述交易字段格式根据不同的应用场景按照以下格式：1）PC网站：传入浏览器打开的网站主页title名-实际商品名称 ；2）公众号：传入公众号名称-实际商品名称；3）H5：传入浏览器打开的移动网页的主页title名-实际商品名称；4）线下门店：门店品牌名-城市分店名-实际商品名称；5）APP：传入应用市场上的APP名字-实际商品名称
		"fee_type":         constkey.FEE_TYPE_CNY,                            //交易币种，目前工行只支持使用人民币（001）支付
		"spbill_create_ip": payInfo.CreateIP,                                 //用户端IP
		"total_fee":        realAmount.Mul(decimal.NewFromInt(100)).String(), //订单金额，单位为分
		"mer_url":          i.notifyUrl,                                      //异步通知商户URL，端口必须为443或80
		"icbc_appid":       i.appId,                                          //商户在工行API平台的APPID
		"union_id":         "",                                               //第三方用户标识，商户在支付宝生活号接入时必送，即access_type为8时，上送用户的唯一标识；商户通过微信公众号内或微信小程序接入时不送
		"mer_acct":         i.merAcct,                                        //商户账号，商户入账账号，只能交易时指定
		"expire_time":      i.payTimeOut,                                     //订单失效时间，单位为秒，建议大于60秒
		"attach":           fmt.Sprintf("%s-%s", title, body),                //附加数据，在查询API和支付通知中原样返回，该字段主要用于商户携带订单的自定义数据
		"order_apd_inf":    "",                                               //订单附加信息
		"detail":           "",                                               //商品详细描述，对于使用单品优惠的商户，该字段必须按照规范上传。微信与支付宝的规范不同，请根据支付方式对应相应的规范上送，详细内容参考文末说明
		"notify_type":      constkey.NOTIFY_TYPE_HS,                          //通知类型，表示在交易处理完成后把交易结果通知商户的处理模式。 取值“HS”：在交易完成后将通知信息，主动发送给商户，发送地址为mer_url指定地址； 取值“AG”：在交易完成后不通知商户
		"result_type":      constkey.RESULT_TYPE_0,                           //结果发送类型，通知方式为HS时有效。取值“0”：无论支付成功或者失败，银行都向商户发送交易通知信息；取值“1”，银行只向商户发送交易成功的通知信息。默认是"0"
		"pay_limit":        "",                                               //支付方式限定，上送”no_credit“表示不支持信用卡支付；上送“no_balance”表示仅支持银行卡支付；不上送或上送空表示无限制
		"return_url":       payInfo.FormUrl,                                  //支付成功回显页面，支付成功后，跳转至该页面显示。当access_type=5且pay_mode=10才有效
		"quit_url":         "",                                               //用户付款中途退出返回商户网站的地址（仅对浏览器内支付时有效）当access_type=5且pay_mode=10才有效
	}
	if payMode == constkey.PAY_MODE_WX {
		bizContentData["shop_appid"] = i.wxAppId //商户在微信开放平台注册的APPID，支付方式为微信时不能为空
	}
	res, err := i.execute(commonData, bizContentData, constkey.UrlConsumepurchase)
	if err != nil {
		return nil, err
	}

	responseBizContent := res.Get(constkey.RESPONSE_BIZ_CONTENT)
	var res2 gjson.Result
	if payMode == constkey.PAY_MODE_ZFB { //支付宝支付
		res2 = responseBizContent.Get(constkey.ZFB_DATA_PACKAGE)
	} else if payMode == constkey.PAY_MODE_WX { //微信支付
		res2 = responseBizContent.Get(constkey.WX_DATA_PACKAGE)
	}
	return &res2, nil
}

//异步回调 InputData raw数据
func (i *icbcpay) Notify(inputData string) (*gjson.Result, error) {
	body, err := i.DisposeNotifyData(inputData)
	if err != nil {
		return nil, err
	}
	return body, nil
}

//内部处理异步通知数据
func (i *icbcpay) DisposeNotifyData(inputData string) (*gjson.Result, error) {
	var (
		body       gjson.Result
		returnCode int64
		returnMsg  string
	)

	params := make(map[string]string)
	dataValues, err := url.ParseQuery(inputData)
	if err != nil {
		return nil, err
	}

	params[constkey.FROM] = dataValues.Get(constkey.FROM)
	params[constkey.API] = dataValues.Get(constkey.API)
	params[constkey.APP_ID] = dataValues.Get(constkey.APP_ID)
	params[constkey.FORMAT] = dataValues.Get(constkey.FORMAT)
	params[constkey.CHARSET] = dataValues.Get(constkey.CHARSET)
	params[constkey.ENCRYPT_TYPE] = dataValues.Get(constkey.ENCRYPT_TYPE)
	params[constkey.TIMESTAMP] = dataValues.Get(constkey.TIMESTAMP)
	params[constkey.SIGN_TYPE] = dataValues.Get(constkey.SIGN_TYPE)
	bizContent := dataValues.Get(constkey.BIZ_CONTENT_KEY)
	body = gjson.Parse(bizContent)
	params[constkey.BIZ_CONTENT_KEY] = bizContent
	returnCode = body.Get(constkey.RETURN_CODE).Int()
	returnMsg = body.Get(constkey.RETURN_MSG).String()
	//异步通知回调验签
	signTxt := i.generateSignToStr(i.getPathByNotifyUrl(i.notifyUrl)+"?", params)
	sign := dataValues.Get(constkey.SIGN)
	if err := i.verifySign(signTxt, sign); err != nil {
		return nil, err
	}
	params[constkey.SIGN] = sign
	if returnCode != 0 {
		logs.Info("异步回调通知失败，错误码：【%d】,错误信息：【%s】", returnCode, returnMsg)
	}
	return &body, nil
}

//执行
func (i *icbcpay) execute(commonData, bizContentData map[string]string, action string) (*gjson.Result, error) {
	bytes, err := json.Marshal(bizContentData)
	if err != nil {
		return nil, err
	}
	bizContent := string(bytes) //未加密
	if i.isNeedEncrypt {
		bizContent, _, err = i.aesEncryptBizContent(bytes) //aes加密
		if err != nil {
			return nil, err
		}
	}
	commonData[constkey.BIZ_CONTENT_KEY] = bizContent
	sign := i.signature(fmt.Sprintf("%s?", action), commonData)
	commonData[constkey.SIGN] = sign

	url := fmt.Sprintf("%s%s", i.host, action)
	resBody, err := i.request(commonData, url, constkey.REQUEST_TYPE_POST)
	if err != nil {
		return nil, err
	}

	result := gjson.Parse(string(resBody))
	responseBizContentResult := result.Get(constkey.RESPONSE_BIZ_CONTENT)
	responseBizContent := responseBizContentResult.String()

	if i.signType == constkey.SIGN_TYPE_CA { //如果是CA签名则对返回的数据先解密，后验签
		responseBizContent, err = i.aesDecryptBizContent(responseBizContent)
	}
	//对请求结果验签
	if err := i.verifySign(responseBizContent, result.Get(constkey.SIGN).String()); err != nil {
		return nil, err
	}

	returnCode := responseBizContentResult.Get(constkey.RETURN_CODE).Int()
	if returnCode != 0 {
		returnMsg := responseBizContentResult.Get(constkey.RETURN_MSG).String()
		logs.Info("调用接口出错，API:【%s】,错误码=【%d】,错误信息：【%s】", action, returnCode, returnMsg)
	}
	return &result, nil
}

//请求
func (i *icbcpay) request(data map[string]string, url string, requestType string) (resBody []byte, err error) {
	req := resty.R().SetHeaders(map[string]string{
		"Content-Type": "application/x-www-form-urlencoded",
		"charset":      "UTF-8",
	}).SetFormData(data)
	var res *resty.Response
	if requestType == constkey.REQUEST_TYPE_POST {
		res, err = req.Post(url)
		if err != nil {
			return
		}
	} else if requestType == constkey.REQUEST_TYPE_GET {
		res, err = req.Get(url)
		if err != nil {
			return
		}
	} else {
		return []byte{}, requestTypeErr
	}

	return res.Body(), nil
}

//消息通讯唯一编号，每次调用独立生成
func GetMsgId() string {
	times := time.Now().In(Loc).UnixNano() / 1000000
	return fmt.Sprintf("%d", times)
}

//获取商户在工行登记app的签名方式
func (i *icbcpay) GetSignType() string {
	return i.signType
}

//交易日期时间
func getOrigDateTime() string {
	now := time.Now()
	str := now.Format(constkey.Date_Time_Format)
	return strings.Replace(str, " ", "T", 1)
}

type QrCodeResponseBizContent struct {
	ReturnCode int    `json:"return_code"`
	ReturnMsg  string `json:"return_msg"`
	MsgId      string `json:"msg_id"`
	Qrcode     string `json:"qrcode"`
	attach     string `json:"attach"`
}

//根据通知url获取path,以便后面通知验签使用
func (i *icbcpay) getPathByNotifyUrl(notifyUrl string) string {
	notifyUrls := strings.Split(notifyUrl, "//")
	if len(notifyUrls) != 2 {
		return ""
	}
	notifyUrl = notifyUrls[1]
	index := strings.Index(notifyUrls[1], "/")
	return notifyUrl[index:]
}

/**
 * 签名
 * prex-有的话格式为/api/xxx?
 * @return string
 */
func (i *icbcpay) signature(prex string /*前缀，默认是空*/, data map[string]string) string {
	sign := ""
	var err error
	orderedParams := i.generateSignToStr(prex, data) //参数排序
	sign, err = i.Sign(orderedParams)
	if err != nil {
		return ""
	}
	return sign
}

/**
 * 生成待签名字符串
 * @return string
 */
func (i *icbcpay) generateSignToStr(prex string /*前缀，默认是空*/, data map[string]string) string {
	keys := make([]string, 0)
	for key, _ := range data {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	signTxt := prex
	for _, key := range keys {
		if value, ok := data[key]; ok {
			if value == "" {
				continue
			} else {
				signTxt += key + "=" + value + "&"
			}
		}
	}
	signTxt = strings.TrimRight(signTxt, "&")
	return signTxt
}

//获取签名字符串
func (i *icbcpay) Sign(data string) (sign string, err error) {
	switch i.signType {
	case constkey.SIGN_TYPE_RSA:
		sign, err = gorsa.SignSha1WithRsa(data, i.myPrivateKey)
		break
	case constkey.SIGN_TYPE_RSA2:
		sign, err = gorsa.SignSha256WithRsa(data, i.myPrivateKey)
		break
	default:
	}
	return
}

//验签
func (i *icbcpay) verifySign(data, sign string) (err error) {
	signType := constkey.SIGN_TYPE_RSA //验签签名只支持RSA方式
	switch signType {
	case constkey.SIGN_TYPE_RSA:
		err = gorsa.VerifySignSha1WithRsa(data, sign, i.apigwPublicKey)
		break
	case constkey.SIGN_TYPE_RSA2:
		err = gorsa.VerifySignSha256WithRsa(data, sign, i.apigwPublicKey)
		break
	default:
		err = icbcVerifySignatureErr
	}
	return
}

//入参：bizContent-原始数据
//出参：encryptBizContent-加密后的数据, encryptKey-加密key
//aes加密bizContent内容
func (i *icbcpay) aesEncryptBizContent(bizContent []byte) (encryptBizContent, encryptKey string, err error) {
	if i.signType == constkey.SIGN_TYPE_CA && i.encryptType == constkey.ENCRYPT_TYPE_AES {
		key := i.aesKey(16)
		encryptKey, err = i.encodeKey(key)
		if err != nil {
			return
		}
		encryptBizContent = i.aesEncrypt(bizContent, []byte(key))
		i.encryptKey = encryptKey
	}
	return
}

//入参：encryptBizContent-加密数据；encryptKey-加密key
//解密bizContent内容
func (i *icbcpay) aesDecryptBizContent(encryptBizContent string) (decryptBizContent string, err error) {
	return i.aesDecrypt(encryptBizContent, i.encryptKey)
}

// 生成aesKey
func (i *icbcpay) aesKey(size int) string {
	res := ""
	for i := 0; i < size; i++ {
		res += string(allchar[rand.Intn(62)])
	}
	return res
}

// Key 编码
func (i *icbcpay) encodeKey(key string) (string, error) {
	return gorsa.PublicEncrypt(key, i.apigwPublicKey)
}

// aes 加密
func (i *icbcpay) aesEncrypt(origData []byte, key []byte) string {
	ciphers, _ := aes.NewCipher(i.generateKey(key))
	length := (len(origData) + aes.BlockSize) / aes.BlockSize
	plain := make([]byte, length*aes.BlockSize)
	copy(plain, origData)
	pad := byte(len(plain) - len(origData))
	for i := len(origData); i < len(plain); i++ {
		plain[i] = pad
	}
	encrypted := make([]byte, len(plain))
	// 分组分块加密
	for bs, be := 0, ciphers.BlockSize(); bs <= len(origData); bs, be = bs+ciphers.BlockSize(), be+ciphers.BlockSize() {
		ciphers.Encrypt(encrypted[bs:be], plain[bs:be])
	}

	return base64.StdEncoding.EncodeToString(encrypted)
}

// aes 解密
func (i *icbcpay) aesDecrypt(data string, key string) (string, error) {
	decrypt, err := gorsa.PriKeyDecrypt(key, i.myPrivateKey)
	if err != nil {
		return "", err
	}
	byteKey := []byte(decrypt)
	byteData, err := base64.StdEncoding.DecodeString(data)
	ciphers, _ := aes.NewCipher(i.generateKey(byteKey))
	decrypted := make([]byte, len(byteData))
	for bs, be := 0, ciphers.BlockSize(); bs < len(byteData); bs, be = bs+ciphers.BlockSize(), be+ciphers.BlockSize() {
		ciphers.Decrypt(decrypted[bs:be], byteData[bs:be])
	}
	trim := 0
	if len(decrypted) > 0 {
		trim = len(decrypted) - int(decrypted[len(decrypted)-1])
	}
	return string(decrypted[:trim]), nil
}

//
func (i *icbcpay) generateKey(key []byte) (genKey []byte) {
	genKey = make([]byte, 16)
	copy(genKey, key)
	for i := 16; i < len(key); {
		for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
			genKey[j] ^= key[i]
		}
	}
	return genKey
}
